#include <buffer/page_allocator.h>
#include <gtest/gtest.h>
#include <option/config.h>
#include <storage/page.h>

#include <cstdint>
#include <cstring>
#include <random>

#include "buffer/buffer_pool_manager.h"
namespace boltDB {
class PageBuilder {
 public:
  PageBuilder(uint32_t page_id, uint32_t overflow, uint16_t page_type,
              uint16_t count)
      : page_id_(page_id),
        overflow_(overflow),
        page_type_(page_type),
        count_(count) {}

  char* BuildFreelistPage() {
    char* data = new char[BOLTDB_PAGE_SIZE];
    memset(data, 0, BOLTDB_PAGE_SIZE);
    // 填充 PageHeader
    memcpy(data, &page_id_, sizeof(page_id_));
    memcpy(data + Page::PageHeader::OVERFLOW_OFFSET, &overflow_,
           sizeof(overflow_));
    memcpy(data + Page::PageHeader::PAGE_TYPE_OFFSET, &page_type_,
           sizeof(page_type_));
    memcpy(data + Page::PageHeader::COUNT_OFFSET, &count_, sizeof(count_));

    // 填充 next_page_id
    uint16_t next_page_id = 4;  // 初始化为0，根据实际情况设置
    uint32_t page_ids[] = {next_page_id, 3, 2,1,0};
    memcpy(data + PAGE_HEADER_SIZE, page_ids, sizeof(page_ids));
    return data;
  }

 private:
  uint32_t page_id_;
  uint32_t overflow_;
  uint16_t page_type_;
  uint16_t count_;
};
TEST(BufferPoolManagerTest, BinaryDataTest) {
  const std::string db_name = "test.db";
  const size_t buffer_pool_size = 10;
  const size_t k = 5;

  std::random_device r;
  std::default_random_engine rng(r());

  constexpr int lower_bound =
      static_cast<int>(std::numeric_limits<char>::min());
  constexpr int upper_bound =
      static_cast<int>(std::numeric_limits<char>::max());
  // No matter if `char` is signed or unsigned by default, this constraint must
  // be met
  static_assert(upper_bound - lower_bound == 255);
  std::uniform_int_distribution<int> uniform_dist(lower_bound, upper_bound);
  // 构造一个buffer_pool缓冲区
  PageBuilder builder = PageBuilder(255, 0xFFFFFFFF, 0x10, 4);
  char* free_list_page = builder.BuildFreelistPage();
  uint16_t* free_page_count = reinterpret_cast<uint16_t*>(
      free_list_page + Page::PageHeader::COUNT_OFFSET);
  Unorded_PageAllocator* allocator=new Unorded_PageAllocator (free_page_count,
                                  free_list_page + PAGE_HEADER_SIZE);
  auto* disk_manager = new DiskManager(db_name);
  auto* bpm =
      new BufferPoolManager(buffer_pool_size, allocator, disk_manager, k);

  page_id_t page_id_temp;
  auto* page0 = bpm->NewPage(&page_id_temp);

  // Scenario: The buffer pool is empty. We should be able to create a new page.
  ASSERT_NE(nullptr, page0);
  EXPECT_EQ(0, page_id_temp);

  char random_binary_data[BOLTDB_PAGE_SIZE];
  // Generate random binary data
  for (char& i : random_binary_data) {
    i = static_cast<char>(uniform_dist(rng));
  }

  // Insert terminal characters both in the middle and at end
  random_binary_data[BOLTDB_PAGE_SIZE / 2] = '\0';
  random_binary_data[BOLTDB_PAGE_SIZE - 1] = '\0';

  // Scenario: Once we have a page, we should be able to read and write content.
  std::memcpy(page0->GetData(), random_binary_data, BOLTDB_PAGE_SIZE);
  EXPECT_EQ(
      0, std::memcmp(page0->GetData(), random_binary_data, BOLTDB_PAGE_SIZE));

  // Scenario: We should be able to create new pages until we fill up the buffer
  // pool.
  for (size_t i = 1; i < buffer_pool_size; ++i) {
    EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp));
  }

  // Scenario: Once the buffer pool is full, we should not be able to create any
  // new pages.
  for (size_t i = buffer_pool_size; i < buffer_pool_size * 2; ++i) {
    EXPECT_EQ(nullptr, bpm->NewPage(&page_id_temp));
  }

  // Scenario: After unpinning pages {0, 1, 2, 3, 4}, we should be able to
  // create 5 new pages
  for (int i = 0; i < 5; ++i) {
    EXPECT_EQ(true, bpm->UnRefPage(i, true));
    bpm->FlushPage(i);
  }
  for (int i = 0; i < 5; ++i) {
    EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp));
    // Unpin the page here to allow future fetching
    bpm->UnRefPage(page_id_temp, false);
  }

  // Scenario: We should be able to fetch the data we wrote a while ago.
  page0 = bpm->FetchPage(0);
  ASSERT_NE(nullptr, page0);
  EXPECT_EQ(0, memcmp(page0->GetData(), random_binary_data, BOLTDB_PAGE_SIZE));
  EXPECT_EQ(true, bpm->UnRefPage(0, true));

  // Shutdown the disk manager and remove the temporary file we created.
  disk_manager->ShutDown();
  
  delete allocator;
  delete bpm;
  delete disk_manager;
  delete free_list_page;
  remove("test.db");
}

TEST(BufferPoolManagerTest, SampleTest) {
  const std::string db_name = "test.db";
  const size_t buffer_pool_size = 10;
  const size_t k = 5;

  PageBuilder builder = PageBuilder(255, 0xFFFFFFFF, 0x10, 4);
  char* free_list_page = builder.BuildFreelistPage();
  uint16_t* free_page_count = reinterpret_cast<uint16_t*>(
      free_list_page + Page::PageHeader::COUNT_OFFSET);
  Unorded_PageAllocator* allocator=new Unorded_PageAllocator (free_page_count,
                                  free_list_page + PAGE_HEADER_SIZE);
  auto* disk_manager = new DiskManager(db_name);
  auto* bpm =
      new BufferPoolManager(buffer_pool_size, allocator, disk_manager, k);

  page_id_t page_id_temp;
  auto *page0 = bpm->NewPage(&page_id_temp);

  // Scenario: The buffer pool is empty. We should be able to create a new page.
  ASSERT_NE(nullptr, page0);
  EXPECT_EQ(0, page_id_temp);

  // Scenario: Once we have a page, we should be able to read and write content.
  snprintf(page0->GetData(), BOLTDB_PAGE_SIZE, "Hello");
  EXPECT_EQ(0, strcmp(page0->GetData(), "Hello"));

  // Scenario: We should be able to create new pages until we fill up the buffer pool.
  for (size_t i = 1; i < buffer_pool_size; ++i) {
    EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp));
  }

  // Scenario: Once the buffer pool is full, we should not be able to create any new pages.
  for (size_t i = buffer_pool_size; i < buffer_pool_size * 2; ++i) {
    EXPECT_EQ(nullptr, bpm->NewPage(&page_id_temp));
  }

  // Scenario: After unpinning pages {0, 1, 2, 3, 4} and pinning another 4 new pages,
  // there would still be one buffer page left for reading page 0.
  for (int i = 0; i < 5; ++i) {
    EXPECT_EQ(true, bpm->UnRefPage(i, true));
  }
  for (int i = 0; i < 4; ++i) {
    EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp));
  }

  // Scenario: We should be able to fetch the data we wrote a while ago.
  page0 = bpm->FetchPage(0);
  ASSERT_NE(nullptr, page0);
  EXPECT_EQ(0, strcmp(page0->GetData(), "Hello"));

  // Scenario: If we unpin page 0 and then make a new page, all the buffer pages should
  // now be pinned. Fetching page 0 again should fail.
  EXPECT_EQ(true, bpm->UnRefPage(0, true));
  EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp));
  EXPECT_EQ(nullptr, bpm->FetchPage(0));

  // Shutdown the disk manager and remove the temporary file we created.
  disk_manager->ShutDown();
  remove("test.db");
  delete allocator;
  delete bpm;
  delete disk_manager;
}


}  // namespace boltDB